/**
	Pulsar engine. Core.
	File manager standard streams - windows extension

	Copyright (C) 2013-2014 Dmitry Dolgov

	This program is free software; you can redistribute it and/or modify it under the terms of the
	GNU General Public License as published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
	without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
	See the GNU General Public License for more details.
*/

#include <pulsar/pulsar.h>
#include <pulsar/src/pulsar_intr.h>

namespace pl
{

static pl_int64 _getFileSize(HANDLE hFile);
static pl_int64 _tellFile(HANDLE hFile);
static pl_int64 _seekFile(HANDLE hFile, pl_int64 pos, SeekInfo where);

} // namespace pl

/**
*/
pl::StreamReadWin::StreamReadWin(const pl::Filepath fileName, pl_uint32 options) :
_hFile(INVALID_HANDLE_VALUE)
{
	DWORD shareOptions;
	DWORD createOptions;
	FileManagerImplWin::_convertParams(options, shareOptions, createOptions);

	_hFile = CreateFile(fileName.get(), GENERIC_READ, shareOptions, NULL, createOptions, FILE_ATTRIBUTE_NORMAL, NULL);

	if (_hFile != INVALID_HANDLE_VALUE && (options & OPEN_FILE_APPEND))
	{
		LARGE_INTEGER distanceToMove;
		distanceToMove.QuadPart = 0;
		LARGE_INTEGER newFilePointer;
		const BOOL rc = SetFilePointerEx(_hFile, distanceToMove, &newFilePointer, FILE_END);
		if (!rc)
		{
			plLogWarn(PL_LOG_NAME, PL_T("SetFilePointerEx failed"));
		}
	}
	return;
}


/**

*/
pl::StreamReadWin::~StreamReadWin()
{
	if (_hFile != INVALID_HANDLE_VALUE)
	{
		CloseHandle(_hFile);
		_hFile = NULL;
	}
	return;
}


/**
	see StreamRead::getFileSize()
*/
pl_int64 pl::StreamReadWin::getFileSize() const
{
	PL_ASSERTW(_hFile != INVALID_HANDLE_VALUE, PL_T("File was not opened"));
	return _getFileSize(_hFile);
}

/**
	see StreamRead::tell()
*/
pl_int64 pl::StreamReadWin::tell() const
{
	PL_ASSERTW(_hFile != INVALID_HANDLE_VALUE, PL_T("File was not opened"));
	return _tellFile(_hFile);
}

/**
	see StreamRead::seek()
*/
pl_int64 pl::StreamReadWin::seek(pl_int64 pos, SeekInfo where)
{
	PL_ASSERTW(_hFile != INVALID_HANDLE_VALUE, PL_T("File was not opened"));
	return _seekFile(_hFile, pos, where);
}

/**
	see StreamRead::isEOF()
*/
bool pl::StreamReadWin::isEOF() const
{
	PL_ASSERTW(_hFile != INVALID_HANDLE_VALUE, PL_T("File was not opened"));
	return _tellFile(_hFile) >= _getFileSize(_hFile);
}

/**
	see StreamRead::readBuffer()
*/
bool pl::StreamReadWin::readBuffer(void* pBuf, pl_int32 itemSize, pl_int32 itemCount, pl_int64* lengthRead /*= NULL*/)
{
	PL_ASSERTW(_hFile != INVALID_HANDLE_VALUE, PL_T("File was not opened"));
	PL_ASSERTW(pBuf != NULL, PL_T("Wrong arguments"));

	pl_int64 lengthReadStub;
	if (lengthRead == NULL)
	{
		lengthRead = &lengthReadStub;
	}

	if (pBuf == NULL || _hFile == INVALID_HANDLE_VALUE)
	{
		*lengthRead = -1;
		return false;
	}

	if (itemSize*itemCount == 0)
	{
		*lengthRead = 0;
		return false;
	}

	DWORD nBytes = 0;
	BOOL rc;

	rc = ReadFile(_hFile, pBuf, (DWORD)(itemSize*itemCount), &nBytes, NULL);

	*lengthRead = (pl_int32)nBytes;
	if (rc && nBytes == (DWORD)(itemSize*itemCount))
	{
		return true;
	}
	return false;
}

/**
	see StreamRead::readString()
*/
bool pl::StreamReadWin::readString(pl_char* pStr, int maxLen, pl_char termChar /*= '\0'*/)
{
	int i;
	DWORD nBytes;
	pl_char nextChar;

	PL_ASSERTW(_hFile != INVALID_HANDLE_VALUE, PL_T("File was not opened"));

	if (pStr == NULL)
	{
		PL_ASSERTW(FALSE, PL_T("Wrong arguments"));
		return false;
	}

	for (i=0; i<maxLen-1; i++)
	{
		if (!ReadFile(_hFile, &nextChar, sizeof(nextChar), &nBytes, NULL) || nBytes == 0)
		{
			pStr[i] = '\0';
			return (i > 0) ? true : false;
		}
		pStr[i] = nextChar;
		if (nextChar == termChar)
		{
			break;
		}
	}
	pStr[i+1] = '\0';
	return true;
}

/**
*/
pl::StreamWriteWin::StreamWriteWin(const pl::Filepath fileName, pl_uint32 options) :
_hFile(INVALID_HANDLE_VALUE),
_maxAlign(0)
{
	DWORD shareOptions;
	DWORD createOptions;

	FileManagerImplWin::_convertParams(options, shareOptions, createOptions);

	_hFile = CreateFile(fileName.get(), GENERIC_WRITE, shareOptions, NULL, createOptions, FILE_ATTRIBUTE_NORMAL, NULL);

	if (_hFile != INVALID_HANDLE_VALUE && (options & OPEN_FILE_APPEND))
	{
		LARGE_INTEGER distanceToMove;
		distanceToMove.QuadPart = 0;
		LARGE_INTEGER newFilePointer;
		const BOOL rc = SetFilePointerEx(_hFile, distanceToMove, &newFilePointer, FILE_END);
		if (!rc)
		{
			plLogWarn(PL_LOG_NAME, PL_T("SetFilePointerEx failed"));
		}
	}
	return;
}

/**
*/
pl::StreamWriteWin::~StreamWriteWin()
{
	if (_hFile != INVALID_HANDLE_VALUE)
	{
		CloseHandle(_hFile);
		_hFile = NULL;
	}
	return;
}

/**
	see StreamWrite::getFileSize()
*/
pl_int64 pl::StreamWriteWin::getFileSize() const
{
	PL_ASSERTW(_hFile != INVALID_HANDLE_VALUE, PL_T("File was not opened"));
	return _getFileSize(_hFile);
}

/**
	see StreamWrite::tell()
*/
pl_int64 pl::StreamWriteWin::tell() const
{
	PL_ASSERTW(_hFile != INVALID_HANDLE_VALUE, PL_T("File was not opened"));
	return _tellFile(_hFile);
}

/**
	\see see StreamWrite::seek()
*/
pl_int64 pl::StreamWriteWin::seek(pl_int64 pos, SeekInfo where)
{
	PL_ASSERTW(_hFile != INVALID_HANDLE_VALUE, PL_T("File was not opened"));

	if (where == FILE_SEEK_ALIGN)
	{
		_maxAlign = pl::max(_maxAlign, (int)pos);
	}
	return _seekFile(_hFile, pos, where);
}

/**
	see StreamWrite::flush()
*/
bool pl::StreamWriteWin::flush()
{
	PL_ASSERTW(_hFile != INVALID_HANDLE_VALUE, PL_T("File was not opened"));

	return FlushFileBuffers(_hFile) ? true : false;
}

/**
	see StreamWrite::writeBuffer()
*/
bool pl::StreamWriteWin::writeBuffer(const void* pBuf, pl_int32 itemSize, pl_int32 itemCount)
{
	int length = itemSize*itemCount;
	PL_ASSERTW(_hFile != INVALID_HANDLE_VALUE, PL_T("File was not opened"));

	DWORD	nBytes;
	if (!WriteFile(_hFile, pBuf, (DWORD)length, &nBytes, NULL))
	{
		return false;
	}
	return (nBytes == (DWORD)length) ? true : false;
}

/**
	see StreamWrite::writeString()
*/
bool pl::StreamWriteWin::writeString(const pl_char* pStr, pl_char termChar /*= '\0'*/)
{
	PL_ASSERTW(_hFile != INVALID_HANDLE_VALUE, PL_T("File was not opened"));

	if (pStr == NULL)
	{
		PL_ASSERTW(FALSE, PL_T("wrong arguments"));
		return false;
	}

	int pos;
	for (pos =0; pStr[pos] != termChar; pos++)
	{
		// Looking for termination char
	}
	return writeBuffer(pStr, sizeof(pl_char), pos+1);
}

/**
*/
pl_int64 pl::_getFileSize(HANDLE hFile)
{
	pl_int64 res;
	DWORD sizeLow;
	DWORD sizeHigh;

	sizeLow = GetFileSize(hFile, &sizeHigh);

	if (sizeLow == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
	{
		return -1;
	}
	res = sizeLow | ((pl_int64)sizeHigh << 32);
	return res;
}

/**
*/
pl_int64 pl::_tellFile(HANDLE hFile)
{
	LARGE_INTEGER posMove;
	LARGE_INTEGER posCur;

	posMove.QuadPart = 0;
	if (!SetFilePointerEx(hFile, posMove, &posCur, FILE_CURRENT))
	{
		return -1;
	}
	return posCur.QuadPart;
}

/**
*/
pl_int64 pl::_seekFile(HANDLE hFile, pl_int64 pos, SeekInfo where)
{
	DWORD dwMoveMethod;

	switch (where)
	{
		case FILE_SEEK_BEGIN:
			dwMoveMethod = FILE_BEGIN;
			break;

		case FILE_SEEK_END:
			dwMoveMethod = FILE_END;
			break;

		case FILE_SEEK_CURRENT:
			dwMoveMethod = FILE_CURRENT;
			break;

		case FILE_SEEK_ALIGN:
			// Align to the nearest value
			{
				PL_ASSERTW(pos >= 1, PL_T("Wrong align factor"));
				dwMoveMethod = FILE_CURRENT;
				pl_int64 align = pos;
				pos = _tellFile(hFile);
				pl::alignValue(pos, align);
			}
			break;

		default:
			PL_ASSERTW(false, PL_T("Wrong move method"));
			return _tellFile(hFile);
	}

	LARGE_INTEGER curDist, newDist;

	curDist.QuadPart = pos;
	if (!SetFilePointerEx(hFile, curDist, &newDist, dwMoveMethod))
	{
		return -1;
	}
	return newDist.QuadPart;
}

//
// End of file 'pulsar_file_stream_win.cpp'
//
